package org.summerframework.component.security.spring.config;

import org.summerframework.component.security.core.config.LoginProperties;
import org.summerframework.component.security.core.service.LoginManage;
import org.summerframework.component.security.spring.SecurityRoleService;
import org.summerframework.component.security.spring.access.RoleAnnotationSecurityMetadataSourceHandler;
import org.summerframework.component.security.spring.exception.AccessDeniedExceptionResolveHandler;
import org.summerframework.component.security.spring.exception.NotLoginAuthenticationExceptionResolveHandler;
import org.summerframework.component.security.spring.exception.UrlAuthenticationFailureHandler;
import org.summerframework.component.security.spring.filter.LogoutHandler;
import org.summerframework.core.util.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.servlet.SpringBootWebSecurityConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
import org.springframework.util.ClassUtils;

import javax.annotation.Resource;

@Slf4j
@EnableWebSecurity
@Configuration
@EnableConfigurationProperties({LoginProperties.class, SecurityProperties.class})
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
@AutoConfigureBefore({SpringBootWebSecurityConfiguration.class, UserDetailsServiceAutoConfiguration.class})
public class SecurityConfiguration<S extends Session> extends WebSecurityConfigurerAdapter implements Ordered {

    private final LoginProperties loginProperties;

    @Autowired(required = false)
    private LoginManage loginManage;

    @Autowired(required = false)
    private SecurityRoleService securityRoleService;

    @Resource
    private SecurityProperties securityProperties;

    @Resource
    private SpringContextUtil springContextUtil;

    @Resource
    private FindByIndexNameSessionRepository<S> sessionRepository;

    public SecurityConfiguration(LoginProperties loginProperties) {
        this.loginProperties = loginProperties;
    }

    @Bean
    public AccessDeniedExceptionResolveHandler accessDeniedExceptionResolveHandler() {
        return new AccessDeniedExceptionResolveHandler();
    }

    @Bean
    public NotLoginAuthenticationExceptionResolveHandler notLoginAuthenticationExceptionResolveHandler() {
        return new NotLoginAuthenticationExceptionResolveHandler(loginProperties);
    }

    @Bean
    public UrlAuthenticationFailureHandler urlAuthenticationFailureHandler() {
        return new UrlAuthenticationFailureHandler();
    }

    @Bean
    @ConditionalOnMissingBean(RoleAnnotationSecurityMetadataSourceHandler.class)
    public RoleAnnotationSecurityMetadataSourceHandler roleAnnotationSecurityMetadataSourceHandler() {
        return new RoleAnnotationSecurityMetadataSourceHandler() {
        };
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭csrf防护
        if (!securityProperties.isCrsf()) {
            http.csrf().disable();
        }

        http.sessionManagement().sessionCreationPolicy(securityProperties.getSessionMode());

        if (!securityProperties.isEnable()) {
            http.authorizeRequests().anyRequest().anonymous();
        } else if (securityProperties.isAny()) {
            http.authorizeRequests().anyRequest().authenticated();
        } else if (securityProperties.getPatterns() != null) {
            http.authorizeRequests().antMatchers(securityProperties.getPatterns()).authenticated();
        } else if (securityProperties.getRequestMatcherClass() != null) {
            http.authorizeRequests().requestMatchers(securityProperties.getRequestMatcherClass().newInstance()).authenticated();
        }

        if (ClassUtils.isPresent("org.springframework.security.oauth2.provider.ClientDetails", this.getClass().getClassLoader())) {
            http.requestMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/oauth/token")));
        }

        http.rememberMe()
                .rememberMeServices(rememberMeServices()).and()
                .exceptionHandling().authenticationEntryPoint(urlAuthenticationFailureHandler())
                .and().sessionManagement().sessionFixation().none()
                .maximumSessions(securityProperties.getMaximumSessions())
                .sessionRegistry(sessionRegistry());

        if (securityProperties.isLogout()) {
            if (loginManage != null) {
                http.logout()
                        .logoutUrl(loginProperties.getLogout())
                        .clearAuthentication(true)
                        .invalidateHttpSession(false)
                        .logoutSuccessHandler(new LogoutHandler(loginManage, urlAuthenticationFailureHandler()))
                        .permitAll()
                        .and();
            } else {
                log.warn("logout not register, loginManager is null");
            }
        }

        if (securityProperties.isLogin()) {
            if (loginManage != null) {
                http.apply(new LoginConfigurer<>())
                        .loginProcessingUrl(loginProperties.getLoginPath())
                        .loginManager(loginManage)
                        .securityRoleService(securityRoleService).permitAll();
            } else {
                log.warn("login not register, loginManager is null");
            }
        }

//        http.addFilterBefore(userFilter, SecurityContextPersistenceFilter.class);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/favicon.ico");

        if (securityProperties.getIgnoring() != null) {
            web.ignoring().antMatchers(securityProperties.getIgnoring());
        }
    }

    @Bean
    public SpringSessionBackedSessionRegistry<S> sessionRegistry() {
        return new SpringSessionBackedSessionRegistry<>(this.sessionRepository);
    }

    @Bean
    @ConditionalOnMissingBean
    public SpringSessionRememberMeServices rememberMeServices() {
        SpringSessionRememberMeServices springSessionRememberMeServices = new SpringSessionRememberMeServices();
        springSessionRememberMeServices.setValiditySeconds(loginProperties.getRememberMeTime());
        return springSessionRememberMeServices;
    }

    @Override
    public int getOrder() {
        return 100;
    }
}
